package com.working.libcommon.util

import android.util.Log
import com.working.libcommon.BuildConfig
import com.working.libcommon.exts.doWithTry
import org.json.JSONArray
import org.json.JSONObject
import java.util.*
import kotlin.reflect.KProperty

fun Any?.loge() = LogUtils.e(this)
fun Any?.logd() = LogUtils.d(this)
fun Any?.logv() = LogUtils.v(this)

object LogUtils {
    /**
     * 打印 Map，生成结构化键值对子串
     * @param space 行缩进量
     */
    private fun <K, V> Map<K, V?>.print(space: Int = 0): String {
        //'生成当前层次的行缩进，用space个空格表示，当前层次每一行内容都需要带上缩进'
        val indent = StringBuilder().apply {
            repeat(space) { append(" ") }
        }.toString()
        return StringBuilder("\n${indent}{").also { sb ->
            this.iterator().forEach { entry ->
                //'如果值是 Map 类型，则递归调用print()生成其结构化键值对子串，否则返回值本身'
                val value = entry.value.let { v ->
                    (v as? Map<*, *>)?.print("${indent}${entry.key} = ".length) ?: v.toString()
                }
                sb.append("\n\t${indent}[${entry.key}] = $value,")
            }
            sb.append("\n${indent}}")
        }.toString()
    }

    private fun Any.ofMap(): Map<String, Any?>? {
        return this::class.takeIf { it.isData }
            ?.members?.filterIsInstance<KProperty<Any>>()
            ?.map { member ->
                val value = member.call(this).let { v ->
                    //'若成员变量是data class，则递归调用ofMap()，将其转化成键值对，否则直接返回值'
                    if (v::class.isData) v.ofMap()
                    else v
                }
                member.name to value
            }
            ?.toMap()
    }

    private const val LOG_MAXLENGTH = 2000

    private const val TAG = "wanAndroid"

    private fun getLogContent(msg: Any?): String {
        return doWithTry {
            when {
                msg == null -> "null"
                msg::class.isData -> msg.ofMap()?.print()
                msg is Throwable -> Log.getStackTraceString(msg)
                msg.toString().startsWith("{") -> "\n" + JSONObject(msg.toString()).toString(1)
                msg.toString().startsWith("[") -> "\n" + JSONArray(msg.toString()).toString(1)
                else -> msg.toString()
            }
        } ?: msg.toString()
    }

    private fun handleMsg(msg: String): List<String> {
        val result: MutableList<String> =
            ArrayList()
        val strLength = msg.length
        var start = 0
        var end = LOG_MAXLENGTH
        for (i in 0..99) {
            //剩下的文本还是大于规定长度则继续重复截取并输出
            if (strLength > end) {
                result.add(msg.substring(start, end))
                start = end
                end += LOG_MAXLENGTH
            } else {
                result.add(msg.substring(start, strLength))
                break
            }
        }
        return result
    }

    fun v(msg: Any?) {
        if (BuildConfig.DEBUG && msg != null) {
            val s = getLogContent(msg)
            for (info in handleMsg(s)) {
                Log.v(
                    TAG,
                    logTitle + info
                )
            }
        }
    }

    fun d(msg: Any?) {
        if (BuildConfig.DEBUG && msg != null) {
            val s = getLogContent(msg)
            for (info in handleMsg(s)) {
                Log.d(
                    TAG,
                    logTitle + info
                )
            }
        }
    }

    fun i(msg: Any?) {
        if (BuildConfig.DEBUG && msg != null) {
            val s = getLogContent(msg)
            for (info in handleMsg(s)) {
                Log.i(
                    TAG,
                    logTitle + info
                )
            }
        }
    }

    fun w(msg: Any?) {
        if (BuildConfig.DEBUG && msg != null) {
            val s = getLogContent(msg)
            for (info in handleMsg(s)) {
                Log.w(
                    TAG,
                    logTitle + info
                )
            }
        }
    }

    fun e(msg: Any?) {
        if (BuildConfig.DEBUG) {
            val s = getLogContent(msg)
            for (info in handleMsg(s)) {
                Log.e(
                    TAG,
                    logTitle + info
                )
            }
        }
    }

    /**
     * make log title
     *
     * @return title_name
     */
    private val logTitle: String
        get() {
            val elm =
                Thread.currentThread().stackTrace[4]
            var className = elm.className
            val dot = className.lastIndexOf('.')
            if (dot != -1) {
                className = className.substring(dot + 1)
            }
            return className + "." + elm.methodName + "(" + elm.lineNumber + ")" + ": "
        }

}